Skip to content

feat: Add async big segment store manager and async Redis adapter#462

Open
jsonbailey wants to merge 8 commits into
mainfrom
jb/sdk-2729/async-big-segments
Open

feat: Add async big segment store manager and async Redis adapter#462
jsonbailey wants to merge 8 commits into
mainfrom
jb/sdk-2729/async-big-segments

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

The async big-segments layer for the async Python SDK.

  • impl/async_big_segments.pyAsyncBigSegmentStoreManager (async twin of the sync manager: membership caching via ExpiringDict, status polling via AsyncRepeatingTask) + BigSegmentStoreStatusProviderImpl.
  • impl/integrations/redis/async_redis_big_segment_store.py — async Redis-backed AsyncBigSegmentStore (redis.asyncio).
  • integrations/__init__.py — new Redis.async_big_segment_store(...) factory (local import of the adapter, so redis stays optional).
  • async_config.pyseeded with AsyncBigSegmentsConfig only (see below).
  • Tests: testing/impl/test_async_big_segments.py (unit) + testing/integrations/test_async_redis.py (live-Redis integration, guarded by skip_database_tests).
  • pyproject.toml — the published [redis] extra floor is unchanged (>=2.10.5); only the dev/test redis dependency is bumped to >=4.2.0 so CI can exercise redis.asyncio. Async Redis support requires redis>=4.2.0, surfaced via a README note and the adapter's guarded import (a clear error if redis.asyncio is unavailable) rather than by raising the consumer-facing floor. Raising the published floor is deferred pending a team decision before the async client ships.

Note on async_config.py

AsyncBigSegmentStoreManager needs AsyncBigSegmentsConfig, which lives in async_config.py. The full AsyncConfig isn't extractable yet (it imports AsyncHook/AsyncPlugin/AsyncEventProcessor, which land in later slices), so this PR seeds async_config.py with just the self-contained AsyncBigSegmentsConfig (it depends only on the already-merged AsyncBigSegmentStore). A later slice grows the file with AsyncConfig — mirroring how the sync SDK keeps BigSegmentsConfig alongside Config in config.py.

Why

Part of the async Python SDK (epic SDK-60). Depends on the async support classes (#451) and async interfaces/store (#457), both merged.

Validation

  • uv run pytest ldclient/testing --ignore=ldclient/testing/integrations — 1033 passed, 2 skipped
  • uv run pytest ldclient/testing/impl/test_async_big_segments.py — 14 passed
  • uv run mypy ldclient — clean; isort / pycodestyle — clean
  • Redis integration tests (test_async_redis.py) require a live Redis (run in CI); properly guarded by skip_database_tests.

SDK-2729 (epic SDK-60).


Note

Medium Risk
Changes affect flag evaluation inputs (Big Segment membership and HEALTHY/STALE/STORE_ERROR status) and add lifecycle/async polling behavior; marked experimental but correctness still matters for targeting.

Overview
Adds the experimental async Big Segments stack for the Python SDK: AsyncBigSegmentsConfig, AsyncBigSegmentStoreManager (membership cache, background status polling via AsyncRepeatingTask, inline poll when status is not ready yet, async stop), and an async Redis AsyncBigSegmentStore exposed as Redis.async_big_segment_store().

Sync code is refactored by moving BigSegmentStoreStatusProviderImpl, EMPTY_MEMBERSHIP, user-key hashing, and stale checks into big_segments_common.py so the sync manager stays behaviorally the same with less duplication.

async_config.py is seeded with only AsyncBigSegmentsConfig ahead of a later AsyncConfig slice. Dev dependency redis is bumped to >=4.2.0 for redis.asyncio. Unit and live-Redis integration tests cover the new manager and adapter.

Reviewed by Cursor Bugbot for commit 45a2125. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds AsyncBigSegmentStoreManager, the async Redis-backed AsyncBigSegmentStore adapter, and Redis.async_big_segment_store(). Seeds async_config.py with the self-contained AsyncBigSegmentsConfig (the full AsyncConfig lands later). Bumps the redis dependency to >=4.2.0 (required by redis.asyncio).
Drop the max_connections=10 default (and the now-unneeded redis_opts copy) so the async store passes redis_opts straight to from_url, matching the sync store. The sync side removed this cap in #387 (it throttled connections under load); redis.asyncio otherwise defaults to an effectively-unlimited pool.
…egments_common

Both big_segments.py and async_big_segments.py defined the class identically (bar one docstring word); move it to a shared module imported by both, matching the evaluator_common/client_common pattern.
Fold _hash_for_user_key, EMPTY_MEMBERSHIP, and is_stale (as a free function) into big_segments_common, imported by both big_segments.py and async_big_segments.py.
Drop the redundant EMPTY_MEMBERSHIP class attribute and reference the
big_segments_common module constant directly instead of via self.
@jsonbailey
jsonbailey marked this pull request as ready for review July 21, 2026 16:24
@jsonbailey
jsonbailey requested a review from a team as a code owner July 21, 2026 16:24
Comment thread pyproject.toml Outdated
[project.optional-dependencies]
async = ["aiohttp>=3.9,<4"]
redis = ["redis>=2.10.5"]
redis = ["redis>=4.2.0,<6.0.0"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not considered a breaking change?

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7bd5ebd. Configure here.

status = self.__last_status
if status is None:
return BigSegmentStoreStatus(False, False)
return status if status else self.poll_store_and_update_status() # type: ignore[return-value]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_status returns unawaited coroutine

Low Severity

In get_status, the fallback calls async poll_store_and_update_status without await, so that branch would return a coroutine object instead of a BigSegmentStoreStatus. The branch is unreachable with current BigSegmentStoreStatus objects, but it mirrors the sync manager incorrectly and is masked by a type ignore.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7bd5ebd. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants